#include "hls_m3u8_parser.h"

#include "./../log_module/log_public_module.h"
#include "hls_m3u8_message.h"
#include "hls_protocol_errno.h"

#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <regex>
#include <string>
#include <strings.h>
#include <vector>

HlsM3u8Parser::HlsM3u8Parser() = default;

void HlsM3u8Parser::InitParser()
{
    m_parser_func_map["#EXTM3U"] = [this](auto && PH1, auto && PH2) { return ExtM3u(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXT-X-TARGETDURATION:"] = [this](auto && PH1, auto && PH2) { return ExtXTargetDuration(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXT-X-VERSION:"] = [this](auto && PH1, auto && PH2) { return ExtXVersion(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXT-X-MEDIA-SEQUENCE:"] = [this](auto && PH1, auto && PH2) { return ExtXMetaSequence(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXT-X-PLAYLIST-TYPE:"] = [this](auto && PH1, auto && PH2) { return ExtXPlayListType(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXTINF:"] = [this](auto && PH1, auto && PH2) { return ExtInf(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXT-X-ENDLIST"] = [this](auto && PH1, auto && PH2) { return ExtXEndList(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
    m_parser_func_map["#EXT-X-STREAM-INF:"] = [this](auto && PH1, auto && PH2) { return ExtXStreamInf(std::forward<decltype(PH1)>(PH1), std::forward<decltype(PH2)>(PH2)); };
}
int HlsM3u8Parser::Parser(const uint8_t * data, int data_length, int & use_length)
{
    if (m_parser_func_map.empty())
    {
        InitParser();
    }
    auto ptr = reinterpret_cast<const char *>(data);
    for (; data_length; data_length--, use_length++)
    {
        char character = *(ptr);
        if (character == ' ' || character == '\n' || character == '\r')
        {
            ptr++;
        }
        else
        {
            break;
        }
    }
    if (!ptr)
    {
        return EmHlsProtocolErrno::EmHlsProtocolErrno_PARSE_BAD;
    }
    if (strlen(ptr) < 7)
    {
        return EmHlsProtocolErrno::EmHlsProtocolErrno_NEED_DATA;
    }
    if (strncasecmp(ptr, "#EXTM3U", 7) != 0)
    {
        return EmHlsProtocolErrno::EmHlsProtocolErrno_PARSE_BAD;
    }
    ClearHlsMessage();
    std::vector<std::string> line_array;
    int use_line_length = 0;
    for (auto p = ptr; p;)
    {
        const char * begin = p;
        const char * end   = strchr(begin, '\n');
        end = end == nullptr ? strchr(begin, '\r') : end;
        if (end - begin > 0)
        {
            std::string line = end ? std::string(begin, end) : std::string(begin);
            line_array.push_back(std::move(line));
        }
        else
        {
            use_line_length += 1;
        }
        p = end ? end + 1 : nullptr;
    }
    use_line_length += line_array.size() - 1;
    int result = 0;
    for (auto & line  : line_array)
    {
        int execute = 0;
        use_line_length += line.length();
        for (auto & item : m_parser_func_map)
        {
            if (line.compare(0, item.first.size(), item.first) == 0)
            {
                execute = 1;
                result = item.second(item.first, line);
                if (result < 0)
                {
                    break;
                }
            }
        }
        if (!execute && !line.empty() && line[0] != '#' && !m_hls_message.HasExtEndList())
        {
            ResourceIndex(line);
        }
        if (result < 0)
        {
            result = EmHlsProtocolErrno::EmHlsProtocolErrno_PARSE_BAD;
            break;
        }
        if (m_hls_message.HasExtEndList())
        {
            break;
        }
    }
    if (result != EmHlsProtocolErrno::EmHlsProtocolErrno_PARSE_BAD)
    {
        result = EmHlsProtocolErrno::EmHlsProtocolErrno_SUCCESS;
        use_length += use_line_length;
    }
    return result;
}

HlsM3U8Message * HlsM3u8Parser::HlsMessage()
{
    return &m_hls_message;
}

void HlsM3u8Parser::ClearHlsMessage()
{
    m_hls_message.Clear();
}

bool HlsM3u8Parser::ProcessStreamInfo(const std::string & raw, HlsM3U8Message::StreamInfo & stream_info)
{
    bool                               result = false;
    std::map<std::string, std::string> field_value_map;
    SplitField(raw, field_value_map, ",");
    if (!field_value_map.empty())
    {
        result = true;
        for (auto & item : field_value_map)
        {
            if (item.first == "PROGRAM-ID")
            {
                stream_info.m_program_id = item.second;
            }
            else if (item.first == "BANDWIDTH")
            {
                stream_info.m_bindwidth = item.second;
            }
            else if (item.first == "RESOLUTION")
            {
                stream_info.m_resolution = item.second;
            }
        }
    }
    else
    {
        result = false;
    }
    return result;
}

void HlsM3u8Parser::SplitField(const std::string & raw, std::map<std::string, std::string> & result_map,
                               const std::string & split)
{
    size_t begin = 0;
    std::string key;
    std::string value;
    for (size_t index = 0; index < raw.size();)
    {
        if (raw[index] == '=')
        {
            key = raw.substr(begin, index - begin);
            begin = index + 1;
            size_t begin_pos = raw.find('"', begin);
            if (raw[begin] != '"')
            {
                auto end_pos = raw.find(',', begin);
                begin = end_pos == std::string::npos ? begin : end_pos;
            }
            else
            {
                auto end_pos = raw.find('"', begin_pos + 1);
                begin = end_pos == std::string::npos ? begin : end_pos + 1;
            }
            value = raw.substr(index + 1, begin - index - 1);
            index = begin;
            begin = index + 1;
            result_map[key] = value;
        }
        else
        {
            index++;
        }
    }
}

int HlsM3u8Parser::ExtM3u(const std::string & prefix, const std::string & line)
{
    (void)this;
    m_hls_message.Clear();
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtM3u clear hls message(%p) data", this, &m_hls_message);
    return 0;
}

int HlsM3u8Parser::ExtXTargetDuration(const std::string & prefix, const std::string & line)
{
    m_hls_message.TargetDuration(line.substr(prefix.size()));
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtXTargetDuration hls message(%p) target duration:%.2lf", this, &m_hls_message, m_hls_message.TargetDuration());
    return 0;
}

int HlsM3u8Parser::ExtXVersion(const std::string & prefix, const std::string & line)
{
    m_hls_message.Version(line.substr(prefix.size()));
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtXVersion hls message(%p) version:%d", this, &m_hls_message, m_hls_message.Version());
    return 0;
}

int HlsM3u8Parser::ExtXMetaSequence(const std::string & prefix, const std::string & line)
{
    m_hls_message.MediaSequence(line.substr(prefix.size()));
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtXMetaSequence hls message(%p) MediaSequence:%d", this, &m_hls_message, m_hls_message.MediaSequence());
    return 0;
}

int HlsM3u8Parser::ExtXPlayListType(const std::string & prefix, const std::string & line)
{
    m_hls_message.PlayListType(line.substr(prefix.size()));
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtXPlayListType hls message(%p) PlayListType:%s", this, &m_hls_message, m_hls_message.PlayListType().c_str());
    return 0;
}

int HlsM3u8Parser::ExtInf(const std::string & , const std::string & line)
{
    if (m_ext_info != line)
    {
        m_ext_info = line;
        LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtInf ext info:%s", this, m_ext_info.c_str());
    }
    return 0;
}

int HlsM3u8Parser::ExtXEndList(const std::string & prefix, const std::string & line)
{
    m_hls_message.UpdateExtEndList(true);
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ExtXEndList hls message(%p) update ext end list", this, &m_hls_message);
    return 0;
}

int HlsM3u8Parser::ExtXStreamInf(const std::string & prefix, const std::string & line)
{
    HlsM3U8Message::StreamInfo info = {};
    if (ProcessStreamInfo(line.substr(prefix.size()), info))
    {
        m_hls_message.StreamInfoData(info);
    }
    return 0;
}

int HlsM3u8Parser::ResourceIndex(const std::string & line)
{
    m_hls_message.UriIndex(m_ext_info, line);
    LOG_HLS_MODULE("HlsM3u8Parser(%p) ResourceIndex hls message(%p) add resource index:%s", this, &m_hls_message, line.c_str());
    return 0;
}

